/*
 * Copyright 2010 Marcin Blazejewski
 *
 * This file is part of Nofate.
 *
 * Nofate is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Nofate is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Nofate. If not, see <http://www.gnu.org/licenses/>.
 *
 */


/*
 * TODO:
 *  - don't use global structures for fov_* routines, move it to am
 */

#include "redeye.h"
#include "font.h"
#include "fov_point.h"
#include <string.h>
#include <math.h>

SDL_Surface *screen = NULL;
static SDL_Surface *overlay = NULL;

static int width = 0;
static int height = 0;


void _render(int *fg, int *bg, unsigned char light, char hl);

int re_init(int screenw, int screenh)
{
    int rc = 0;
    
    if (screen) return 0;
    
    width = screenw;
    height = screenh;
    
    if (rc == 0)
    {
        rc = SDL_Init( SDL_INIT_VIDEO | SDL_INIT_TIMER );
        if (rc != 0)
        {
            printf("RE: Error: SDL_Init: %s\n", SDL_GetError());
        }
    }
  
    if (rc == 0)
    {
        // Set video mode   
        screen = SDL_SetVideoMode(screenw * CELL_SIZE, screenh * CELL_SIZE, DISPLAY_BPP, SDL_SWSURFACE);
        if (screen == NULL)
        {
            fprintf(stderr, "RE: Error: Set video mode: %s\n", SDL_GetError());
            rc = -1;
        }
        //SDL_WM_SetCaption("", NULL);  
    }
    
    if (rc == 0)
    {
        overlay = SDL_CreateRGBSurface(SDL_SWSURFACE | SDL_SRCALPHA, screen->w, screen->h,
            32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
        
        if (overlay)
        {
             SDL_FillRect(overlay, NULL, 0);
        } else
        {
            printf("overlay failed\n");
        }
    }
    
    if (rc == 0)
    {
        
        rc = font_init();
    }
    
    return rc;
}

void re_end()
{
    if (!screen) return;
    font_end();
    fov_end();
       
    //TTF_Quit();
    SDL_Quit();
}

void re_flush()
{
    SDL_Flip(screen);
}

int hsv2rgb(int h, int s, int v)
{
    if (s > 0xFF) s = 0xFF;
    if (v > 0xFF) v = 0xFF;
    int r, g, b;
    float sn = s / 255.0f;
    
    int hi = ((int)floor(h / 60)) % 6;
    float f = (h / 60.0f) - floor(h / 60.0f);
    float p = v * (1.0f - sn);
    float q = v * (1.0f - f * sn);
    float t = v * (1.0f - (1.0f - f) * sn);

    switch (hi)
    {
        case 0: r = (int)v; g = (int)t; b = (int)p; break;
        case 1: r = (int)q; g = (int)v; b = (int)p; break;
        case 2: r = (int)p; g = (int)v; b = (int)t; break;
        case 3: r = (int)p; g = (int)q; b = (int)v; break;
        case 4: r = (int)t; g = (int)p; b = (int)v; break;
        case 5: r = (int)v; g = (int)p; b = (int)q; break;    
    }
    
    return (r << 16) | (g << 8) | b;
}

void re_draw_view(MapView *view, int ox, int oy, int w, int h)
{
    SDL_Surface *display = screen;
    list<Thing*>::iterator i;
    int x, y;
    MapCell *c;
    int fg, bg, ch;
    unsigned char light, state;

    if (ox >= view->source.width) return;
    if (oy >= view->source.height) return;

    // limit by viewport size, main window size and src area size (incl view offset)
    int maxx = (w < width) ? w: width;
    if ( (maxx + ox) > view->source.width ) maxx = (view->source.width - ox);
    int maxy = (h < height) ? h: height;
    if ( (maxy + oy) > view->source.height ) maxy = (view->source.height - oy);
    
    // TERRAIN
    for (y = 0; y < maxy; y++)
    {
        for (x = 0; x < maxx; x++)
        {
            state = view->fovmap[ox + x][oy + y];
            
            /* is the tile visible? */
            if ((state == FOV_LIT) || (state == FOV_OBSTACLE) || (state == FOV_FOG))
            {
                /* get tile data */
                c = &view->source.cells[ox + x][oy + y];
                bg = c->bg;
                fg = c->terrain->fg;
                ch = c->terrain->ch;
                light = c->light;
                
                _render(&fg, &bg, light, ((state == FOV_LIT) || (state == FOV_OBSTACLE)) );
                re_putchar(display, x, y, ch, fg, bg);
            }
            else
            {
                re_putchar(display, x, y, ' ', 0x000000, 0);
                continue;
            }
            
        }
    }

    // THINGS
    for (i = view->source.things.begin(); i != view->source.things.end(); i++) {
        Thing *t = (Thing*)*i;
        state = view->fovmap[t->x][t->y];
        if ((state != FOV_LIT) && (state != FOV_OBSTACLE)) continue;

        x = t->x - ox;
        y = t->y - oy;
        if ((x < 0) || (y < 0) || (x >= w) || (y >= h)) continue;

        c = &view->source.cells[t->x][t->y];
        bg = c->bg;
        light = c->light;
        fg = t->fg;
        ch = t->ch;

        _render(&fg, &bg, light, 1);
        re_putchar(display, x, y, ch, fg, bg);
    }        
    
    SDL_BlitSurface(overlay, NULL, screen, NULL);
    //SDL_Delay(300);
}

void _render(int *fg, int *bg, unsigned char light, char hl) {
    int fg_h, fg_s, fg_v;
    int bg_h, bg_s, bg_v;

    /* extract fg HSV components */
    fg_h = *fg >> 16;
    fg_s = (*fg >> 8) & 0x000000FF;
    fg_v = *fg & 0x000000FF;
    
    /* extract bg HSV components */
    bg_h = *bg >> 16;
    bg_s = (*bg >> 8) & 0x000000FF;
    bg_v = *bg & 0x000000FF;
    
    /* darken fg and bg colors according to light level 
     * on that cell */
    fg_v *= light;
    fg_v /= 100;
    bg_v *= light;
    bg_v /= 100;
    fg_s *= light;
    fg_s /= 100;
    bg_s *= light;
    bg_s /= 100;
    
    /* highlight background of cells in current FOV */
    if (hl) {
        bg_v += 10;
    } 
    
    /* convert HSV to RGB integer */
    *fg = hsv2rgb(fg_h, fg_s, fg_v);
    *bg = hsv2rgb(bg_h, bg_s, bg_v);
}

void re_puts(int x, int y, int fg, int bg, const char *s)
{
    if (!s) return;
    if ((x<0) || (y<0)) return;
    
    const char *ch = s;
    while ( *ch )
    {
        re_putchar(screen, x++, y, *(ch++), fg, bg);
        if (x >= width) {x=0; y++;}
        if (y >= height) return;
    }
}

void reov_clear()
{
    SDL_FillRect(overlay, NULL, 0x00000000);
}

#define OV_ALPHA 0xFF000000
void reov_line(int x0, int y0, int x1, int y1, unsigned int color)
{
    SDL_Surface *surface = overlay;
    Uint32 *pixels = (Uint32*)surface->pixels;
    color |= OV_ALPHA;
    
    if (SDL_MUSTLOCK(surface)) SDL_LockSurface(surface);
    
    int p0 = x0;
    int p1 = x1;
    int q0 = y0;
    char p_is_x = 1;
    
    int dx = (x1 - x0);
    int dy = (y1 - y0);
    int plim = width * CELL_SIZE;
    int qlim = height * CELL_SIZE;
    
    float a;
    if ((dx != 0) && (dx > dy))
    {
        a = (float)dy / (float)dx;
    } else
    {
        a = (float)dx / (float)dy;
        p0 = y0;
        p1 = y1;
        q0 = x0;
        p_is_x = 0;
        plim = height * CELL_SIZE;
        qlim = width * CELL_SIZE;
    }
    
    int p = p0, q = 0;
    int dp = (p1 > p0) ? 1 : -1;
    while (p != p1)
    {
        q = (int)round( q0 + (p - p0) * a );
        if ((p >= 0) && (q >= 0) && (p < plim) && (q < qlim)) 
        {
            if (p_is_x)
            {
                pixels[ (q * surface->pitch/4) + p ] = color;
            }
            else
            {
                pixels[ (p * surface->pitch/4) + q ] = color;
            }
        }
        
        p += dp;
    }
    
    if (SDL_MUSTLOCK(surface)) SDL_UnlockSurface(surface);
}

void reov_rect(int x0, int y0, int x1, int y1, unsigned int color)
{
    SDL_Surface *surface = overlay;
    Uint32 *pixels = (Uint32*)surface->pixels;
    color |= OV_ALPHA;
    
    int x, y;
    int dx = (x1 > x0) ? 1 : -1;
    int dy = (y1 > y0) ? 1 : -1;
    
    if (SDL_MUSTLOCK(surface)) SDL_LockSurface(surface);
    
    for (x = x0; x != x1; x+=dx)
    {
        pixels[ (y0 * surface->pitch/4) + x ] = color;
        pixels[ (y1 * surface->pitch/4) + x ] = color;
    }
    for (y = y0; y != y1; y+=dy)
    {
        pixels[ (y * surface->pitch/4) + x0 ] = color;
        pixels[ (y * surface->pitch/4) + x1 ] = color;
    }
    
    if (SDL_MUSTLOCK(surface)) SDL_UnlockSurface(surface);
}

void reov_fillrect(int x0, int y0, int x1, int y1, unsigned int color)
{
    color |= OV_ALPHA;
    SDL_Rect r = { x0, y0, (x1-x0), (y1-y0) };
    SDL_FillRect(overlay, &r, color);
}




int _debug_wait()
{
    SDL_Event ev;
    SDL_WaitEvent(&ev);
    if (ev.type == SDL_QUIT) return 1;
    
    return 0;
}

void _debug_loop(int fps)
{
    Uint32 o0, o1, ft = 0, frames = 0;  // perf. counters
    Uint32 t0, t1, dd;  // frame time counters
    
    // desired delay - time of single frame to get requested fps
    dd = 1000 / fps;
    
    o0 = SDL_GetTicks();
    
    SDL_Event ev;
    while (1)
    {
        t0 = SDL_GetTicks();
        if (SDL_PollEvent(&ev))
        {
            if (ev.type == SDL_QUIT) break;
        }
                        
        re_flush();
        frames++;
        
        t1 = SDL_GetTicks();
        Uint32 delta = t1 - t0;
        if ((t1-t0) < dd) SDL_Delay(dd - delta);
        ft += delta;
    }
    
    o1 = SDL_GetTicks();
    printf("AVG FPS %.2f\n", frames * 1000.0f / (o1-o0));
    printf("AVG frame time %d\n", (ft / frames));
}
